<script setup lang="ts">
import { ISchema, Schema } from '@formily/vue';
import { ITreeNode, TreeNode } from '@season-designable/core';
import { useTree } from '@season-designable/vue';
import { isString } from 'lodash-es';
import MonacoInput from './components/MonacoInput/index.vue';

export interface ITransformerOptions {
  designableFieldName?: string;
  designableFormName?: string;
}

export interface IFormilySchema {
  schema?: ISchema;
  form?: Record<string, any>;
}

const tree = useTree();

const createOptions = (options: ITransformerOptions): ITransformerOptions => {
  return {
    designableFieldName: 'Field',
    designableFormName: 'Root',
    ...options,
  };
};

const findNode = (node: TreeNode, finder?: (node: TreeNode) => boolean) => {
  if (!node) return;
  if (finder?.(node)) return node;
  if (!node.children.value) return;
  for (let i = 0; i < node.children.value.length; i++) {
    if (findNode(node.children.value[i])) return node.children.value[i];
  }
  return;
};

const transformToSchema = (
  node: TreeNode,
  options?: ITransformerOptions
): any => {
  const realOptions = createOptions(options || {});
  const root = findNode(node, (child) => {
    return child.componentName === realOptions.designableFormName;
  });

  const schema: ISchema = {
    type: 'object',
    properties: {},
  };

  if (!root) return { schema };

  const createSchema = (node: TreeNode, schema: ISchema = {}) => {
    if (node !== root) {
      Object.assign(schema, node.props.value);
    }

    schema['x-designable-id'] = node.id;
    if (schema.type === 'array') {
      if (node.children.value[0]) {
        if (
          node.children.value[0].componentName ===
          realOptions.designableFieldName
        ) {
          schema.items = createSchema(node.children.value[0]);
          schema['x-index'] = 0;
        }
      }
      node.children.value.slice(1).forEach((child, index) => {
        if (child.componentName !== realOptions.designableFieldName) return;
        const key = child.props.value?.name || child.id;
        schema.properties = schema.properties || {};
        if (!isString(schema.properties)) {
          schema.properties[key] = createSchema(child);
          schema.properties[key]['x-index'] = index;
        }
      });
    } else {
      node.children.value.forEach((child, index) => {
        if (child.componentName !== realOptions.designableFieldName) return;
        const key: string = child.props.value?.name || child.id;
        if (!schema.properties) schema.properties = {};

        if (!isString(schema.properties)) {
          schema.properties[key] = createSchema(child);
          schema.properties[key]['x-index'] = index;
        }
      });
    }
    return schema;
  };
  return { form: root.props.value, schema: createSchema(root, schema) };
};

const transformToTreeNode = (
  formily: IFormilySchema = {},
  options?: ITransformerOptions
) => {
  const realOptions = createOptions(options || {});
  const root: ITreeNode = {
    componentName: realOptions.designableFormName,
    props: formily.form,
    children: [],
  };

  if (formily.schema) {
    const schema = new Schema(formily.schema);

    const cleanProps = (props: any) => {
      if (props['name'] !== props['x-designable-id']) {
        delete props.name;
      }

      delete props['version'];
      delete props['_isJSONSchemaObject'];

      return props;
    };

    const appendTreeNode = (parent: ITreeNode, schema: Schema) => {
      if (!schema) return;
      const current = {
        id: schema['x-designable-id'],
        componentName: realOptions.designableFieldName,
        props: cleanProps(schema.toJSON(false)),
        children: [],
      };

      parent.children?.push(current);
      schema.mapProperties((schema) => {
        appendTreeNode(current, schema);
      });
    };

    schema.mapProperties((schema) => {
      appendTreeNode(root, schema);
    });
    return root;
  }
};

const onChange = (value: string) => {
  // console.log(transformToTreeNode(JSON.parse(value)));
  const payload = transformToTreeNode(JSON.parse(value));

  if (payload) {
    tree?.from(payload);
  }
};
</script>

<template>
  <MonacoInput
    v-if="tree"
    :value="JSON.stringify(transformToSchema(tree), null, 2)"
    @onChange="onChange"
  ></MonacoInput>
</template>

<style lang="scss" scoped></style>
